﻿using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Windows;
using OpenLS.Spreadsheet.Drawing;

namespace OpenLS.Spreadsheet.Formats.Biff.OfficeArt
{
    internal class OfficeArtClientAnchorSheet : OfficeArtRecord
    {
        private ushort _colL;
        private ushort _colR;
        
        private short _dxL;
        private short _dxR;
        
        private short _dyB;
        private short _dyT;
        
        private bool _fMove;
        private bool _fSize;
        
        private ushort _rwB;
        private ushort _rwT;
        private ushort _reserved;

        private const double widthFactor = 1024d;

        public override RecType RecordType
        {
            get { return RecType.OfficeArtClientAnchorSheet; }
        }

        private ushort Ae
        {
            get
            {
                ushort result = _reserved;
                if (_fMove)
                    result |= 0x01;
                if (_fSize)
                    result |= 0x02;
                return result;
            }
        }

        protected override byte Version
        {
            get { return 0x0; }
        }

        protected override int ByteLength
        {
            get
            {
                if(alternateRawData != null)
                    return alternateRawData.Length;
                return 18;
            }
        }

        private byte[] alternateRawData;
        protected override void ReadNoHeader(OfficeArtReadContext context)
        {
          //  Contract.Requires(rh.recType == RecType.OfficeArtClientAnchorSheet);
         //   Contract.Requires(rhrecLen == ByteLength);
            Contract.Ensures(rhrecLen ==
                 context.BaseStream.Position - Contract.OldValue(context.BaseStream.Position));
           //\\ Contract.Ensures(rhrecLen == ByteLength);
            if (rhrecLen != 18)
            {
                alternateRawData = context.ReadBytes((int) rhrecLen);
                return;
            }
            ushort ae = context.ReadUInt16();
            _fMove = (ae & 0x01) != 0;
            _fSize = (ae & 0x02) != 0;
            _reserved = (ushort) (ae & 0xFF1c);
            Contract.Assert(ae == Ae);
            _colL = context.ReadUInt16();
            _dxL = context.ReadInt16();
            _rwT = context.ReadUInt16();
            _dyT = context.ReadInt16();
            _colR = context.ReadUInt16();
            _dxR = context.ReadInt16();
            _rwB = context.ReadUInt16();
            _dyB = context.ReadInt16();
            Contract.Assert(_colL <= _colR);
        }


        protected override void WriteNoHeader(OfficeArtWriteContext context)
        {
            if (this.alternateRawData != null)
            {
                context.Write(alternateRawData);
                return;
            }
            Contract.Assert(_colL <= _colR);
            context.Write(Ae);
            context.Write(_colL);
            context.Write(_dxL);
            context.Write(_rwT);
            context.Write(_dyT);
            context.Write(_colR);
            context.Write(_dxR);
            context.Write(_rwB);
            context.Write(_dyB);
        }

        private const double heightFactor = 1024;

        static double GetHeight(Row row, int fraction)
        {
            return row.ActualHeight * fraction / heightFactor; 
        }

        static double GetWidth(Column row, int fraction)
        {
            return row.ActualWidth * fraction / widthFactor;
        }
        public static OfficeArtClientAnchorSheet FromAnchor(Anchor anchor)
        {
            Contract.Ensures(Contract.Result<OfficeArtClientAnchorSheet>() != null);
            Contract.Ensures(Contract.Result<OfficeArtClientAnchorSheet>().IsValid);
            if (anchor is TwoCellAnchor)
            {
                TwoCellAnchor twoCellAnchor = (TwoCellAnchor) anchor;
                Worksheet worksheet = twoCellAnchor.Worksheet;
                int row1 = twoCellAnchor.Cell.Row;
                int column1 = twoCellAnchor.Cell.Column;

                int row2 = twoCellAnchor.SecondCell.Row;
                int column2 = twoCellAnchor.SecondCell.Column;

                var result = new OfficeArtClientAnchorSheet
                {
                    _fMove = false,
                    _fSize = false,
                    _rwT = (ushort)row1,
                    _rwB = (ushort)row2,
                    _colL = (ushort)column1,
                    _colR = (ushort)column2,

                    _dxL = (short)(twoCellAnchor.ColumnOffset * widthFactor / worksheet.Columns[twoCellAnchor.Cell.Column].ActualWidth),
                    _dxR = (short)((worksheet.Columns[column1].Offset + twoCellAnchor.ColumnOffset + twoCellAnchor.ActualWidth - worksheet.Columns[column2].Offset) * widthFactor / worksheet.Columns[column2].ActualWidth),
                    _dyT = (short)(twoCellAnchor.RowOffset * heightFactor / worksheet.Rows[twoCellAnchor.Cell.Row].ActualHeight),
                    _dyB = (short)((worksheet.Rows[row1].Offset + twoCellAnchor.RowOffset + twoCellAnchor.ActualHeight - worksheet.Rows[row2].Offset) * heightFactor / worksheet.Rows[row2].ActualHeight),
                };
                return result;

            }
            else if (anchor is OneCellAnchor)
            {
                OneCellAnchor oneCellAnchor = (OneCellAnchor)anchor;
                Worksheet worksheet = oneCellAnchor.Worksheet;
                int row1 = oneCellAnchor.Cell.Row;
                int column1 = oneCellAnchor.Cell.Column;

                int row2 = worksheet.Rows.GetIndexForOffset(worksheet.Rows[oneCellAnchor.Cell.Row].Offset + oneCellAnchor.RowOffset + oneCellAnchor.ActualHeight);
                int column2 = worksheet.Columns.GetIndexForOffset(worksheet.Columns[oneCellAnchor.Cell.Column].Offset + oneCellAnchor.ColumnOffset +  oneCellAnchor.ActualWidth);
                var result = new OfficeArtClientAnchorSheet
                                 {
                                     _fMove = false,
                                     _fSize = true,
                                     _rwT = (ushort)oneCellAnchor.Cell.Row,
                                     _rwB = (ushort) row2,
                                     _colL = (ushort)oneCellAnchor.Cell.Column,
                                     _colR = (ushort) column2,

                                     _dxL = (short)(oneCellAnchor.ColumnOffset * widthFactor / worksheet.Columns[oneCellAnchor.Cell.Column].ActualWidth),
                                     _dxR = (short)((worksheet.Columns[column1].Offset + oneCellAnchor.ColumnOffset +  oneCellAnchor.ActualWidth - worksheet.Columns[column2].Offset) * widthFactor / worksheet.Columns[column2].ActualWidth),
                                     _dyT = (short)(oneCellAnchor.RowOffset * heightFactor / worksheet.Rows[oneCellAnchor.Cell.Row].ActualHeight),
                                     _dyB = (short)((worksheet.Rows[row1].Offset + oneCellAnchor.RowOffset +  oneCellAnchor.ActualHeight - worksheet.Rows[row2].Offset) * heightFactor / worksheet.Rows[row2].ActualHeight),
                                 };
                return result;
            }
            else if (anchor is AbsoluteAnchor)
            {
                AbsoluteAnchor absoluteAnchor = (AbsoluteAnchor)anchor;
                var worksheet = absoluteAnchor.Worksheet;
                int row1 = worksheet.Rows.GetIndexForOffset(absoluteAnchor.RowOffset);
                int column1 = worksheet.Columns.GetIndexForOffset(absoluteAnchor.ColumnOffset);
                int row2 = worksheet.Rows.GetIndexForOffset(absoluteAnchor.RowOffset + absoluteAnchor.ActualHeight);
                int column2 = worksheet.Columns.GetIndexForOffset(absoluteAnchor.ColumnOffset + absoluteAnchor.ActualWidth);
                var result = new OfficeArtClientAnchorSheet
                                 {
                                     _fMove = true,
                                     _fSize = true,
                                     _rwB = (ushort)row2,
                                     _rwT = (ushort)row1,
                                     _colL = (ushort)column1,
                                     _colR = (ushort) column2,
                                     _dyT = (short)((absoluteAnchor.RowOffset - worksheet.Rows[row1].Offset) * heightFactor / worksheet.Rows[row1].ActualHeight),
                                     _dyB = (short)((absoluteAnchor.RowOffset + absoluteAnchor.ActualHeight - worksheet.Rows[row2].Offset) * heightFactor / worksheet.Rows[row2].ActualHeight),
                                     _dxL = (short)((absoluteAnchor.ColumnOffset - worksheet.Columns[column1].Offset) * widthFactor / worksheet.Columns[column1].ActualWidth),
                                     _dxR = (short)((absoluteAnchor.ColumnOffset + absoluteAnchor.ActualWidth - worksheet.Columns[column2].Offset) * widthFactor / worksheet.Columns[column2].ActualWidth)
                                 };
                return result;
            }
            else
            {
                throw new NotSupportedException();
            }

        }

        private bool IsValid
        {
            get { return _colL <= _colR && _rwT <= _rwB; }
        }

#if DEBUG
        [ContractInvariantMethod]
        private void Invariant()
        {
            Contract.Invariant(_colL <= _colR);
            Contract.Invariant(_rwT <= _rwB);

        }
#endif
        public Anchor ToAnchor(Worksheet worksheet)
        {
            Contract.Requires(worksheet != null);
            Contract.Ensures(Contract.Result<Anchor>() != null);
#if DEBUG
          //\\  Contract.Ensures(this.Equivalent(FromAnchor(Contract.Result<Anchor>())));
#endif   
            Contract.Assert(_colL <= _colR);
            if (!_fMove)
            {
                if (!_fSize)
                {
                    var result = new TwoCellAnchor(worksheet);
                    result.Cell = new CellAddress(_rwT, _colL);
                    result.SecondCell = new CellAddress(_rwB, _colR);
                    result.ColumnOffset = GetWidth(worksheet.Columns[_colL], _dxL);
                    result.RowOffset = GetHeight(worksheet.Rows[_rwT], _dyT);
                    result.SetToOffset(new Point(GetWidth(worksheet.Columns[_colR], _dxR), GetHeight(worksheet.Rows[_rwB], _dyB)));
#if DEBUG
                    Contract.Assert(this.Equivalent2(FromAnchor(result)));
#endif
                    return result;
                }
                else
                {
                    var result = new OneCellAnchor(worksheet);
                    result.Cell = new CellAddress(_rwT, _colL);
                    result.ColumnOffset = GetWidth(worksheet.Columns[_colL], _dxL);
                    result.RowOffset = GetHeight(worksheet.Rows[_rwT], _dyT);
                    result.Width = GetWidth(worksheet.Columns[_colR], _dxR) - GetWidth(worksheet.Columns[_colL], _dxL) +
                                   worksheet.Columns[_colR].Offset - worksheet.Columns[_colL].Offset;
                    result.Height = GetHeight(worksheet.Rows[_rwB], _dyB) - GetHeight(worksheet.Rows[_rwT], _dyT) +
                                    worksheet.Rows[_rwB].Offset - worksheet.Rows[_rwT].Offset;
                    Contract.Assert(worksheet.Rows.GetIndexForOffset(worksheet.Rows[result.Cell.Row].Offset + result.RowOffset +  result.ActualHeight) == _rwB);
#if DEBUG
                    Contract.Assert(this.Equivalent2(FromAnchor(result)));
#endif
                    return result;
                }
            }
            else
            {
                //Contract.Assert(fSize);
                var result = new AbsoluteAnchor(worksheet);
                result.ColumnOffset = worksheet.Columns[_colL].Offset + GetWidth(worksheet.Columns[_colL], _dxL);
                result.RowOffset = worksheet.Rows[_rwT].Offset + GetHeight(worksheet.Rows[_rwT], _dyT);
                result.ActualWidth = GetWidth(worksheet.Columns[_colR], _dxR) - GetWidth(worksheet.Columns[_colL], _dxL) +
                                     worksheet.Columns[_colR].Offset - worksheet.Columns[_colL].Offset;
                result.ActualHeight = GetHeight(worksheet.Rows[_rwB], _dyB) - GetHeight(worksheet.Rows[_rwT], _dyT) +
                                      worksheet.Rows[_rwB].Offset - worksheet.Rows[_rwT].Offset;
#if DEBUG
                var other = FromAnchor(result);
             //\\   Contract.Assert(other._rwT == this._rwT);
               //\\ Contract.Assert(this.Equivalent2(FromAnchor(result)));
#endif
                return result;
            }
        }

#if DEBUG

        private bool Equivalent(OfficeArtClientAnchorSheet other)
        {
            Contract.Requires(other != null);
            Contract.Ensures(Contract.Result<bool>());
            return other._fMove == this._fMove && (true || other._fSize == this._fSize) && other._colL == this._colL && other._colR == this._colR && other._rwB == this._rwB &&
                   other._rwT == this._rwT && other._dxL == this._dxL && other._dxR == this._dxR &&
                   other._dyT == this._dyT && other._dyB == this._dyB;
        }
        private bool Equivalent2(OfficeArtClientAnchorSheet other)
        {
            Contract.Requires(other != null);
            var result =  other._fMove == this._fMove && (true || other._fSize == this._fSize) && other._colL == this._colL && other._colR == this._colR && other._rwB == this._rwB &&
                   other._rwT == this._rwT && other._dxL == this._dxL && other._dxR == this._dxR &&
                   Math.Abs(other._dyT - this._dyT) <= 1 && other._dyB == this._dyB;
            Contract.Assert(result);
            return result;
        }
#endif
    }
}